`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/08/26 14:14:48
// Design Name: 
// Module Name: ddr4_writer
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////
/* 接口时序
wr_clk _/`\__/`\__/`\__/`\__/`\__/`\__/`\__/`\__/`\__/`\__/`\__/`\__/`\__/`\_
wr_req ______/````\_________________________________________________________
wr_busy___________/```````````````````````````````````````\_________________
wr_addr xxxxx|vvvv|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
wr_len  xxxxx|vvvv|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
wr_data |xxxxxxxxxxxxxxxxxxx|vvvv|vvvv|vvvv|...vv|xxxxxxxxxxxxxxxxxxxxxxxxxx
wr_valid ___________________/```````````````...``\__________________________
wr_ready ______________/````````````````````...``\__________________________
wr_ddr_done _________________________________________/````\_________________
wr_status xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|vvvv|xxxxxxxxxxxxxxxxx 
*/

module ddr4_writer#(
    //DDR芯片数据接口位宽
    parameter   DDR4_DATA_WIDTH         = 16                            ,
    //DDR MIG 用户侧axi接口的地址位宽
    parameter   DDR4_ADDR_WIDTH         = 32                            ,
    //DDR MIG 用户侧axi接口的数据位宽
    parameter   USER_DATA_WIDTH         = 128                           ,
    //axi接口突发写的长度，协议规定最大256
    parameter   AXI_MM_BURST_LEN        = 64                            ,
    //DDR4的容量，单位是字节
    parameter   DDR4_CAPASITY           = 32'h4000_0000                 
)(
    //用户接口
    //用户写时钟                                                               
    input   wire                        wr_clk                          , 
    input   wire                        wr_rst                          , 
    //写请求
    input   wire                        wr_req                          , 
    //写地址，在写请求拉高的时候有效
    input   wire    [DDR4_ADDR_WIDTH-1:0]wr_addr                        ,      
    //写长度，在写请求拉高的时候有效
    input   wire    [15:0]              wr_len                          ,
    //写数据
    input   wire    [USER_DATA_WIDTH-1:0]wr_data                        ,
    input   wire                        wr_valid                        ,
    output  logic                       wr_ready                        ,
    //写busy状态
    output  logic                       wr_busy                         ,
    //写完成信号
    output  logic                       wr_ddr_done                     ,
    //写完成状态 00:ok  01:无效写请求地址或者长度 10:axi 写失败
    output  logic   [1:0]               wr_status                       ,

    //DDR在axi总线域的基地址
    input   wire    [31:0]              WR_BASE_ADDR                    ,
    //axi-mm写接口
    output  logic    [31:0]              WR_AXI_0_awaddr                 ,
    output  wire    [1:0]               WR_AXI_0_awburst                ,
    output  wire    [3:0]               WR_AXI_0_awcache                ,
    output  logic    [7:0]               WR_AXI_0_awlen                  ,
    output  wire    [0:0]               WR_AXI_0_awlock                 ,
    output  wire    [2:0]               WR_AXI_0_awprot                 ,
    output  wire    [3:0]               WR_AXI_0_awqos                  ,
    input   wire                        WR_AXI_0_awready                ,
    output  wire    [2:0]               WR_AXI_0_awsize                 ,
    output  logic                        WR_AXI_0_awvalid                ,
    output  wire                        WR_AXI_0_bready                 ,
    input   wire    [1:0]               WR_AXI_0_bresp                  ,
    input   wire                        WR_AXI_0_bvalid                 ,
    output  logic    [USER_DATA_WIDTH-1:0]WR_AXI_0_wdata                 ,
    output  logic                        WR_AXI_0_wlast                  ,
    input   wire                        WR_AXI_0_wready                 ,
    output  wire    [USER_DATA_WIDTH/8-1:0]WR_AXI_0_wstrb               ,
    output  wire                         WR_AXI_0_wvalid                ,

    //debug 当前模块有个问题，如果写入数据的数量小于请求的长度，模块状态机会一直卡在写数据的地方，必须保证写入数据与请求的长度相同
    output  logic  [15:0]               dbg_recived_data_num            ,
    output  logic  [15:0]               dbg_request_data_num            
);
    
    //寄存写地址
    logic   [DDR4_ADDR_WIDTH-1:0]       reg_wr_addr                     ;
    //寄存写长度
    logic   [15:0]                      reg_wr_len                      ;
    //当前已经写入DDR的数量
    logic   [15:0]                      cur_wr_cnt                      ;
    //本次突发写的总长度
    logic   [15:0]                      cur_burst_len                   ;
    //本次突发已经写的长度
    logic   [15:0]                      cur_burst_cnt                   ;
    //输入fifo写接口使能标志
    logic                               fifo_write_en_flag              ;
    //ddr axi写接口使能标志
    logic                               ddr_write_en_flag               ;

    //输入fifo相关信号
    logic                               fifo_rst                        ;   
    logic   [USER_DATA_WIDTH-1:0]       fifo_wr_data                    ;
    logic                               fifo_wr_valid                   ;
    logic                               fifo_wr_ready                   ;
    logic   [USER_DATA_WIDTH-1:0]       fifo_rd_data                    ;
    logic                               fifo_rd_valid                   ;
    logic                               fifo_rd_ready                   ;
    logic   [10:0]                      rd_data_count_axis              ;
    logic   [10:0]                      wr_data_count_axis              ;

    //fifo写接口直接连接模块端口写数据信号，通过fifo_write_en_flag标志来控制接口的连接，防止写入无效数据
    assign fifo_wr_valid = (fifo_write_en_flag == 1)?wr_valid:0;
    assign fifo_wr_data = wr_data;
    assign wr_ready = (fifo_write_en_flag == 1)?fifo_wr_ready:0;

    //debug 信号输出
    assign dbg_request_data_num = reg_wr_len;

    always_ff@(posedge wr_clk) begin
        if(wr_rst) begin
            dbg_recived_data_num <= 0;
        end
        else begin
            if(wr_busy == 1) begin
                if(fifo_wr_valid ==1 && fifo_wr_ready == 1) begin
                    dbg_recived_data_num <= dbg_recived_data_num + 1;
                end
                else begin
                    dbg_recived_data_num <= dbg_recived_data_num;
                end
            end 
            else begin
                dbg_recived_data_num <= 0;
            end
        end
    end
    
    //aximm接口固定不变的信号
    assign WR_AXI_0_bready = 1'b1;
    assign WR_AXI_0_awburst = 2'b01;//自增突发模式
    assign WR_AXI_0_awcache = 4'b0000;
    assign WR_AXI_0_awlock = 2'b00;//访问类型 00：正常访问
    assign WR_AXI_0_awprot = 3'b000;//访问权限
    assign WR_AXI_0_awqos = 4'b0000;
    //awsize指每个transfer（传输一个数据）的突发长度，单位是字节，由数据位宽决定，字节: 000=1   001=2  010=4  011=8 100=16 101=32 110=64 111=128
    //而awlen指的是每个transaction(传输一次数据)的突发长度，单位是数据的个数，awlen的值+1表示突发长度
    assign WR_AXI_0_awsize = $clog2(USER_DATA_WIDTH>>3);
    assign WR_AXI_0_wstrb = {USER_DATA_WIDTH/8{1'b1}};

    assign WR_AXI_0_wdata = fifo_rd_data;
    assign WR_AXI_0_wvalid = (ddr_write_en_flag==1)?fifo_rd_valid:0;
    assign fifo_rd_ready = (ddr_write_en_flag==1)?WR_AXI_0_wready:0;


    //状态机状态定义
    typedef enum logic[2:0] {
        IDLE,
        CHECK_ADDR,
        WR_ADDR,
        WAIT_DATA,
        BURST_WR,
        WAIT_REP,
        WR_END
      } wr_state_t;

    wr_state_t                          wr_state                        ;

    //写ddr状态机
    always_ff @( posedge wr_clk) begin
        if(wr_rst == 1) begin
            wr_ddr_done <= 0;
            wr_status <= 0;
            wr_busy <= 1;

            fifo_rst <= 1;
            fifo_write_en_flag <= 0;
            ddr_write_en_flag <= 0;

            WR_AXI_0_awaddr <= 0;
            WR_AXI_0_awlen <= 0;
            WR_AXI_0_awvalid <= 0;
            WR_AXI_0_wlast <= 0;
       
            cur_wr_cnt <= 0;

            wr_state <= IDLE;
        end
        else begin
            case (wr_state)
                IDLE : 
                begin
                    wr_ddr_done <= 0;
                    cur_wr_cnt <= 0;
                    cur_burst_cnt <= 0;
                    fifo_write_en_flag <= 0;
                    ddr_write_en_flag <= 0;
                    wr_status <= 0;

                    WR_AXI_0_awaddr <= 0;
                    WR_AXI_0_awlen <= 0;
                    WR_AXI_0_awvalid <= 0;
                    WR_AXI_0_wlast <= 0;

                    //空闲状态下接收到写请求信号，暂存本次写请求的地址核长度，并拉高busy，不再响应写请求
                    if(wr_busy == 0 && wr_req == 1) begin
                        reg_wr_addr <= wr_addr;
                        reg_wr_len <= wr_len;                     
                        wr_busy <= 1;
                        wr_state <= CHECK_ADDR;
                    end
                    else begin 
                        wr_busy <= 0;
                        wr_state <= IDLE;
                    end
                end
                CHECK_ADDR : 
                 begin
                    //检测本次写请求的长度和地址是否有效，如果有效进入写地址状态，如果无效，则直接将status置1，进入写结束状态
                    if(reg_wr_addr>= WR_BASE_ADDR && reg_wr_addr < WR_BASE_ADDR+DDR4_CAPASITY
                        && reg_wr_len > 0 && reg_wr_len <= (DDR4_CAPASITY>>($clog2(USER_DATA_WIDTH>>3)))) begin
                        
                        //判断fifo是否为空，如果为空才能进入后面的写模块，如果不为空，则清除fifo
                        if(wr_data_count_axis == 0 && rd_data_count_axis == 0) begin
                            fifo_rst <= 0;
                            fifo_write_en_flag <= 1;
                            wr_state <= WR_ADDR;
                        end
                        else begin
                            fifo_rst <= 1;
                            fifo_write_en_flag <= 0;
                            wr_state <= CHECK_ADDR; 
                        end
                            
                    end
                    //请求无效
                    else begin
                        wr_status <= 1;
                        wr_state <= WR_END;
                    end
                end

                WR_ADDR : 
                begin
                    //如果当前的已经写入的数据量等于本次写请求的长度，意味着完成了所有数据的写入，跳转到WR_END状态
                    if(cur_wr_cnt >= reg_wr_len) begin
                        //清除fifo，为下一次写请求做准备
                        fifo_rst <= 1;
                        wr_state <= WR_END;
                    end
                    //如果还没写完，启动一次突发写，写地址=本次请求的地址+已经写入的计数值*每个数据的字节数量（axi是字节地址）
                    else begin
                        WR_AXI_0_awaddr <= reg_wr_addr + (cur_wr_cnt<<($clog2(USER_DATA_WIDTH/8)));
                        WR_AXI_0_awvalid <= 1;
                        //确定本次突发写长度，如果剩余的长度大于固定的突发长度参数，就用参数，如果小于突发长度，则突发成都为剩余的长度
                        if(reg_wr_len - cur_wr_cnt >= AXI_MM_BURST_LEN) begin
                            WR_AXI_0_awlen <= AXI_MM_BURST_LEN-1;
                            cur_burst_len <= AXI_MM_BURST_LEN;
                        end
                        else begin
                            WR_AXI_0_awlen <= reg_wr_len - cur_wr_cnt-1;
                            cur_burst_len <= reg_wr_len - cur_wr_cnt;
                        end

                        //等待写地址ready信号拉高后进入等待数据阶段
                        if(WR_AXI_0_awvalid == 1 && WR_AXI_0_awready == 1) begin
                            WR_AXI_0_awvalid <= 0;   
                            cur_burst_cnt <= 0;                   
                            wr_state <= WAIT_DATA;
                        end
                        else begin
                            wr_state <= WR_ADDR;
                        end

                        
                    end
                end

                WAIT_DATA : 
                begin
                    //等待fifo中的数据大于等于一次突发长度，进入写数据状态
                    if(rd_data_count_axis >= cur_burst_len)begin
                        ddr_write_en_flag <= 1;
                        //如果突发长度为1，那么直接拉高last信号
                        if(cur_burst_len == 1) begin
                            WR_AXI_0_wlast <= 1;
                        end
                        else begin
                            WR_AXI_0_wlast <= 0;
                        end

                        wr_state <= BURST_WR;
                    end
                    else begin
                        ddr_write_en_flag <= 0;
                        wr_state <= WAIT_DATA;
                    end
                end

                BURST_WR :
                begin
                    //本次突发长度为1
                    if(cur_burst_len == 1) begin
                        if(WR_AXI_0_wvalid == 1 & WR_AXI_0_wready == 1) begin
                            cur_wr_cnt <= cur_wr_cnt + 1;
                            WR_AXI_0_wlast <= 0;
                            wr_state <= WAIT_REP;
                        end
                        else begin
                            wr_state <= BURST_WR;
                        end
                    end
                    //本次突发长度大于1
                    if(cur_burst_len > 1) begin
                        //完成一次突发写
                        if(cur_burst_cnt >= cur_burst_len-1) begin
                            cur_burst_cnt <= 0;
                            ddr_write_en_flag <= 0;
                            WR_AXI_0_wlast <= 0;

                            wr_state <= WAIT_REP;
                        end
                        else begin
                            //每写入一个数计数器+1，在最后一个数据的时候last信号拉高
                            if(WR_AXI_0_wvalid == 1 & WR_AXI_0_wready == 1) begin
                                cur_burst_cnt <= cur_burst_cnt + 1;
                                cur_wr_cnt <= cur_wr_cnt + 1;
                                if(cur_burst_cnt >= cur_burst_len-2) begin
                                    WR_AXI_0_wlast <= 1;
                                end
                                else begin
                                    WR_AXI_0_wlast <= 0;
                                end
                            end
                            wr_state <= BURST_WR;
                        end
                    end
                    
                end

                WAIT_REP :
                begin
                    //等待ddrmig回应信号
                    if(WR_AXI_0_bvalid == 1) begin
                        wr_state <= WR_ADDR;
                        if(WR_AXI_0_bresp != 0) begin
                            wr_status <= 2;
                        end
                        
                    end
                    else begin
                        wr_state <= WAIT_REP;
                    end
                end

                WR_END :
                begin
                    //拉高完成信号
                    wr_ddr_done <= 1;
                    //解除fifo复位状态
                    fifo_rst <= 0;
                    wr_state <= IDLE;
                end

                default: wr_state <= IDLE;
            endcase
           
        end
    end

    xpm_fifo_axis #(
        .CASCADE_HEIGHT(0),             // DECIMAL
        .CDC_SYNC_STAGES(2),            // DECIMAL
        .CLOCKING_MODE("common_clock"), // String
        .ECC_MODE("no_ecc"),            // String
        .FIFO_DEPTH(256),              // DECIMAL
        .FIFO_MEMORY_TYPE("auto"),      // String
        .PACKET_FIFO("false"),          // String
        .PROG_EMPTY_THRESH(10),         // DECIMAL
        .PROG_FULL_THRESH(1000),          // DECIMAL
        .RD_DATA_COUNT_WIDTH(10),        // DECIMAL
        .RELATED_CLOCKS(0),             // DECIMAL
        .SIM_ASSERT_CHK(0),             // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
        .TDATA_WIDTH(USER_DATA_WIDTH),               // DECIMAL
        .TDEST_WIDTH(1),                // DECIMAL
        .TID_WIDTH(1),                  // DECIMAL
        .TUSER_WIDTH(1),                // DECIMAL
        .USE_ADV_FEATURES("1000"),      // String
        .WR_DATA_COUNT_WIDTH(10)         // DECIMAL
    )
    xpm_fifo_axis_inst (
        .almost_empty_axis(almost_empty_axis),   // 1-bit output: Almost Empty : When asserted, this signal
                                                // indicates that only one more read can be performed before the
                                                // FIFO goes to empty.

        .almost_full_axis(almost_full_axis),     // 1-bit output: Almost Full: When asserted, this signal
                                                // indicates that only one more write can be performed before
                                                // the FIFO is full.

        .dbiterr_axis(),             // 1-bit output: Double Bit Error- Indicates that the ECC
                                                // decoder detected a double-bit error and data in the FIFO core
                                                // is corrupted.

        .m_axis_tdata(fifo_rd_data),             // TDATA_WIDTH-bit output: TDATA: The primary payload that is
                                                // used to provide the data that is passing across the
                                                // interface. The width of the data payload is an integer number
                                                // of bytes.

        .m_axis_tdest(),             // TDEST_WIDTH-bit output: TDEST: Provides routing information
                                                // for the data stream.

        .m_axis_tid(),                 // TID_WIDTH-bit output: TID: The data stream identifier that
                                                // indicates different streams of data.

        .m_axis_tkeep(),             // TDATA_WIDTH/8-bit output: TKEEP: The byte qualifier that
                                                // indicates whether the content of the associated byte of TDATA
                                                // is processed as part of the data stream. Associated bytes
                                                // that have the TKEEP byte qualifier deasserted are null bytes
                                                // and can be removed from the data stream. For a 64-bit DATA,
                                                // bit 0 corresponds to the least significant byte on DATA, and
                                                // bit 7 corresponds to the most significant byte. For example:
                                                // KEEP[0] = 1b, DATA[7:0] is not a NULL byte KEEP[7] = 0b,
                                                // DATA[63:56] is a NULL byte

        .m_axis_tlast(),             // 1-bit output: TLAST: Indicates the boundary of a packet.
        .m_axis_tstrb(),             // TDATA_WIDTH/8-bit output: TSTRB: The byte qualifier that
                                                // indicates whether the content of the associated byte of TDATA
                                                // is processed as a data byte or a position byte. For a 64-bit
                                                // DATA, bit 0 corresponds to the least significant byte on
                                                // DATA, and bit 0 corresponds to the least significant byte on
                                                // DATA, and bit 7 corresponds to the most significant byte. For
                                                // example: STROBE[0] = 1b, DATA[7:0] is valid STROBE[7] = 0b,
                                                // DATA[63:56] is not valid

        .m_axis_tuser(),             // TUSER_WIDTH-bit output: TUSER: The user-defined sideband
                                                // information that can be transmitted alongside the data
                                                // stream.

        .m_axis_tvalid(fifo_rd_valid),           // 1-bit output: TVALID: Indicates that the master is driving a
                                                // valid transfer. A transfer takes place when both TVALID and
                                                // TREADY are asserted

        .prog_empty_axis(),       // 1-bit output: Programmable Empty- This signal is asserted
                                                // when the number of words in the FIFO is less than or equal to
                                                // the programmable empty threshold value. It is de-asserted
                                                // when the number of words in the FIFO exceeds the programmable
                                                // empty threshold value.

        .prog_full_axis(),         // 1-bit output: Programmable Full: This signal is asserted when
                                                // the number of words in the FIFO is greater than or equal to
                                                // the programmable full threshold value. It is de-asserted when
                                                // the number of words in the FIFO is less than the programmable
                                                // full threshold value.

        .rd_data_count_axis(rd_data_count_axis), // RD_DATA_COUNT_WIDTH-bit output: Read Data Count- This bus
                                                // indicates the number of words available for reading in the
                                                // FIFO.

        .s_axis_tready(fifo_wr_ready),           // 1-bit output: TREADY: Indicates that the slave can accept a
                                                // transfer in the current cycle.

        .sbiterr_axis(),             // 1-bit output: Single Bit Error- Indicates that the ECC
                                                // decoder detected and fixed a single-bit error.

        .wr_data_count_axis(wr_data_count_axis), // WR_DATA_COUNT_WIDTH-bit output: Write Data Count: This bus
                                                // indicates the number of words written into the FIFO.

        .injectdbiterr_axis(), // 1-bit input: Double Bit Error Injection- Injects a double bit
                                                // error if the ECC feature is used.

        .injectsbiterr_axis(), // 1-bit input: Single Bit Error Injection- Injects a single bit
                                                // error if the ECC feature is used.

        .m_aclk(wr_clk),                         // 1-bit input: Master Interface Clock: All signals on master
                                                // interface are sampled on the rising edge of this clock.

        .m_axis_tready(fifo_rd_ready),           // 1-bit input: TREADY: Indicates that the slave can accept a
                                                // transfer in the current cycle.

        .s_aclk(wr_clk),                         // 1-bit input: Slave Interface Clock: All signals on slave
                                                // interface are sampled on the rising edge of this clock.

        .s_aresetn(~fifo_rst),                   // 1-bit input: Active low asynchronous reset.
        .s_axis_tdata(fifo_wr_data),             // TDATA_WIDTH-bit input: TDATA: The primary payload that is
                                                // used to provide the data that is passing across the
                                                // interface. The width of the data payload is an integer number
                                                // of bytes.

        .s_axis_tdest(0),             // TDEST_WIDTH-bit input: TDEST: Provides routing information
                                                // for the data stream.

        .s_axis_tid(0),                 // TID_WIDTH-bit input: TID: The data stream identifier that
                                                // indicates different streams of data.

        .s_axis_tkeep(16'hffff),             // TDATA_WIDTH/8-bit input: TKEEP: The byte qualifier that
                                                // indicates whether the content of the associated byte of TDATA
                                                // is processed as part of the data stream. Associated bytes
                                                // that have the TKEEP byte qualifier deasserted are null bytes
                                                // and can be removed from the data stream. For a 64-bit DATA,
                                                // bit 0 corresponds to the least significant byte on DATA, and
                                                // bit 7 corresponds to the most significant byte. For example:
                                                // KEEP[0] = 1b, DATA[7:0] is not a NULL byte KEEP[7] = 0b,
                                                // DATA[63:56] is a NULL byte

        .s_axis_tlast(1'b0),             // 1-bit input: TLAST: Indicates the boundary of a packet.
        .s_axis_tstrb(16'h0000),             // TDATA_WIDTH/8-bit input: TSTRB: The byte qualifier that
                                                // indicates whether the content of the associated byte of TDATA
                                                // is processed as a data byte or a position byte. For a 64-bit
                                                // DATA, bit 0 corresponds to the least significant byte on
                                                // DATA, and bit 0 corresponds to the least significant byte on
                                                // DATA, and bit 7 corresponds to the most significant byte. For
                                                // example: STROBE[0] = 1b, DATA[7:0] is valid STROBE[7] = 0b,
                                                // DATA[63:56] is not valid

        .s_axis_tuser(0),             // TUSER_WIDTH-bit input: TUSER: The user-defined sideband
                                                // information that can be transmitted alongside the data
                                                // stream.

        .s_axis_tvalid(fifo_wr_valid)            // 1-bit input: TVALID: Indicates that the master is driving a
                                                // valid transfer. A transfer takes place when both TVALID and
                                                // TREADY are asserted

    );

endmodule
